use std::ops::{Deref, DerefMut};
use std::process::{exit, Command};

pub struct Veth {
    name: String,
    ip: Option<String>,
    ns: Option<String>,
}
impl Deref for Veth {
    type Target = String;

    fn deref(&self) -> &Self::Target {
        &self.name
    }
}

impl DerefMut for Veth {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.name
    }
}

impl Veth {
    pub fn set_ns(&mut self, pid: String) {
        let mut child = Command::new("ip")
            .args(["link", "set", &self, "netns", &pid])
            .spawn()
            .expect("failed to run netns");
        let status = child.wait().expect("failed to wait on netns");
        if !status.success() {
            exit(status.code().unwrap_or(3));
        }
        self.ns = Some(pid)

    }
    pub fn up(&self) {
        if self.ns.is_some() {
            self.up_with_ns()
        } else {
            self.up_without_ns()
        }
    }
    pub fn up_with_ns(&self) {
        let ns = self.ns.clone().expect("failed to get ns");
        let mut child = Command::new("nsenter")
            .args(["-t", &ns, "-n", "ip", "link", "set", &self, "up"])
            .spawn()
            .expect("failed to execute nsenter");
        let status = child.wait().expect("failed to wait on nsenter");
        if !status.success() {
            exit(status.code().unwrap_or(3));
        }
    }
    pub fn up_without_ns(&self) {
        let mut child = Command::new("ip")
            .args(["link", "set", &self, "up"])
            .spawn()
            .expect("failed to execute ip link up");
        let status = child.wait().expect("failed to wait on ip link up");
        if !status.success() {
            exit(status.code().unwrap_or(2));
        }
    }
    pub fn down(&self) {
        if self.ns.is_some() {
            self.down_with_ns()
        } else {
            self.down_without_ns()
        }
    }
    pub fn down_with_ns(&self) {
        let ns = self.ns.clone().expect("failed to get ns");
        let mut child = Command::new("nsenter")
            .args(["-t", &ns, "-n", "ip", "link", "set", &self, "down"])
            .spawn()
            .expect("failed to execute nsenter");
        let status = child.wait().expect("failed to wait on nsenter");
        if !status.success() {
            exit(status.code().unwrap_or(3));
        }
    }
    pub fn down_without_ns(&self) {
        let mut child = Command::new("ip")
            .args(["link", "set", &self, "down"])
            .spawn()
            .expect("failed to execute ip link up");
        let status = child.wait().expect("failed to wait on ip link up");
        if !status.success() {
            exit(status.code().unwrap_or(2));
        }
    }
    pub fn ip(&mut self, ip_and_netmask: String) {
        if self.ns.is_some() {
            self.ip_with_ns(ip_and_netmask)
        } else {
            self.ip_without_ns(ip_and_netmask)
        }
    }
    pub fn ip_without_ns(&mut self, ip_and_netmask: String) {
        let mut child = Command::new("ip")
            .args(["addr", "add", &ip_and_netmask, "dev", &self])
            .spawn()
            .expect("failed to execute ip addr add");
        let status = child.wait().expect("failed to wait on ip addr add");
        if !status.success() {
            exit(status.code().unwrap_or(3));
        }
        self.ip = Some(ip_and_netmask)
    }
    pub fn ip_with_ns(&mut self, ip_and_netmask: String) {
        let ns = self.ns.clone().expect("failed to get ns");
        let mut child = Command::new("nsenter")
            .args([
                "-t",
                &ns,
                "-n",
                "ip",
                "addr",
                "add",
                &ip_and_netmask,
                "dev",
                &self,
            ])
            .spawn()
            .expect("failed to execute ip addr add");
        let status = child.wait().expect("failed to wait on ip addr add");
        if !status.success() {
            exit(status.code().unwrap_or(2));
        }
        self.ip = Some(ip_and_netmask)
    }
}
pub struct VethPeer {
    veth0: Veth,
    veth1: Veth,
    pid: String,
}
impl VethPeer {
    pub fn new(pid: String) -> VethPeer {
        let mut veth = "v".to_string();
        let mut veth0 = veth.clone();
        let mut veth1 = veth.clone();
        veth0.push_str(pid.as_str());
        veth1.push_str(pid.as_str());
        veth0.push_str("_0");
        veth1.push_str("_1");
        Self {
            veth0: Veth {
                name: veth0,
                ip: None,
                ns: None,
            },
            veth1: Veth {
                name: veth1,
                ip: None,
                ns: None,
            },
            pid,
        }
    }
    pub fn get_veth0(& mut self) -> &mut Veth {
        &mut self.veth0
    }
    pub fn get_veth1(& mut self) -> &mut Veth {
        & mut self.veth1
    }
}

impl VethPeer {
    pub fn veth_create_peer_netns0(&self) {
        let mut child = Command::new("ip")
            .args(&[
                "link",
                "add",
                "name",
                &self.veth0,
                "type",
                "veth",
                "peer",
                "name",
                &self.veth1,
            ])
            .spawn()
            .expect("failed to execute create veth peer");
        let status = child.wait().expect("failed to wait on child");
        if !status.success() {
            exit(status.code().unwrap_or(3));
        }
    }
}
